package com.xuelangyun.form.common.redis.lock.aspect;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

import com.xuelangyun.form.common.cache.aspect.CustomCacheStandardEvaluationContext;
import com.xuelangyun.form.common.exception.DyformException;
import com.xuelangyun.form.common.redis.RedisOperateUtils;
import com.xuelangyun.form.common.redis.lock.annotation.RedisLockConfig;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

/**
 * 锁的切面编程<br/>
 * 针对添加@RedisLockConfig 注解的方法进行加锁
 *
 * @author weiqing.huang
 * @version 1.0
 * @date 2024年7月18日17:49:12
 */
@Component
@Aspect
public class LockAopAspect {
    //是否开启redis缓存  true开启   false关闭
    @Value("${spring.redis.open: false}")
    private boolean open = false;

    @Autowired
    private RedisOperateUtils redisUtil;

    private static final String DEFAULT_PREFIX = "_tr_lock_";

    ExpressionParser ep = new SpelExpressionParser();
    CustomCacheStandardEvaluationContext context = new CustomCacheStandardEvaluationContext();

    private Logger logger = LoggerFactory.getLogger(getClass());


    @Around("@annotation(redisLockConfig)")
    public Object doAround(ProceedingJoinPoint joinPoint, RedisLockConfig redisLockConfig) throws Throwable {
        if (!open) {
            logger.warn("未开启redis,reids同步锁失效！");
            Object res = joinPoint.proceed();
            return res;
        }

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        // 获得当前访问的class
        Class<?> className = joinPoint.getTarget().getClass();
        // 获得访问的方法名
        String methodName = joinPoint.getSignature().getName();
        // 得到方法的参数的类型
        Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        Object[] args = joinPoint.getArgs();
        String key = "";
        int expireTime = 30;//秒
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);
            method.setAccessible(true);
            // 判断是否存在@RedisLock注解
            if (method.isAnnotationPresent(RedisLockConfig.class)) {
                key = redisLockConfig.key();
                boolean classPrefix = redisLockConfig.classNamePrefixKey();
                //请求的参数
                String[] paramNames = signature.getParameterNames();
                if (StringUtils.isBlank(key)) {
                    // key为空 就拼所有参数得key-value
                    key = className.getName() + method.getName();

                } else if (key.contains("'") || key.contains("#")) {
                    // 判断key是否有表达式
                    key = (classPrefix ? className.getName() + method.getName() : "") + getEpValueFromCache(key, method.getName(), joinPoint.getTarget().getClass().getSimpleName(), paramNames, args);
                }


                key = DEFAULT_PREFIX + ":" + key;
                expireTime = getExpireTime(redisLockConfig);
            }
        } catch (Exception e) {
            throw new RuntimeException("redis分布式锁注解参数异常", e);
        }
        Object res = new Object();
        if (redisUtil.lock(key, expireTime, TimeUnit.SECONDS)) {
            try {
                res = joinPoint.proceed();
                return res;
            } catch (Exception e) {
                throw new DyformException(e.getMessage());
            } finally {
                redisUtil.unlock(key);
            }
        } else {
            throw new DyformException("数据已被锁定，请刷新页面");
        }
    }

    /**
     * getEpValueFromCache
     *
     * @param script
     * @param method
     * @param className
     * @param argNames
     * @param argValues
     * @return String
     */
    private synchronized String getEpValueFromCache(String script, String method, String className, String[] argNames, Object[] argValues) {

        context.clearVals();
        for (int i = 0; i < argNames.length; i++) {
            context.setVariable(argNames[i], argValues[i]);
            context.setVariable("p" + i, argValues[i]);
        }
        context.setVariable("method", method);
        context.setVariable("className", className);

        Expression e = ep.parseExpression(script);
        Object value = e.getValue(context);
        return (value == null) ? "" : value.toString();
    }


    private int getExpireTime(RedisLockConfig annotation) {
        return annotation.expireTime();
    }

}
